package main

import (
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"log"
	"math/rand"
	"sync"
	"sync/atomic"
	"time"
)

type SecKillModel struct {
	Id      uint32 `json:"id"`
	Amount  uint32 `json:"amount"`
	Version uint32 `json:"version"`
}

type updateFields map[string]interface{}

var (
	mysqlPool sync.Pool
	total     uint32
	success   uint32
	wg        sync.WaitGroup
)

func main() {
	var db *gorm.DB
	dbi := mysqlPool.Get()
	if dbi == nil {
		log.Panicf("poll error")
		return
	}
	db = dbi.(*gorm.DB)
	db.Table("sec_kill").Save(&SecKillModel{Amount: 50, Version: 0, Id: 1})
	db.Table("sec_kill").Save(&SecKillModel{Amount: 50, Version: 0, Id: 2})
	mysqlPool.Put(dbi)

	startTime := time.Now()
	for i := 1; i < 101; i++ {
		wg.Add(1)
		go func(i uint32) {
			defer wg.Done()
			optimisticLock(i)
		}(uint32(i))
	}
	wg.Wait()
	log.Printf("乐观锁全部抢购完成，共有: %d, 成功: %d,用时:%f\n", total, success, float64(time.Now().Sub(startTime))/float64(time.Second))

	total = 0
	success = 0
	startTime = time.Now()
	for i := 1; i < 101; i++ {
		wg.Add(1)
		go func(i uint32) {
			defer wg.Done()
			pessimisticLock(i)
		}(uint32(i))
	}
	wg.Wait()
	log.Printf("悲观锁全部抢购完成，共有: %d, 成功: %d,用时:%f\n", total, success, float64(time.Now().Sub(startTime))/float64(time.Second))
}

func init() {
	//数据库连接
	mysqlPool.New = func() interface{} {
		db, err := gorm.Open("mysql", "clannadxr:a123456@/sec_kill?charset=utf8&parseTime=True&loc=Local&allowNativePasswords=true")
		//db.LogMode(true)
		if err != nil {
			log.Panicf("connect to mysql error:%+v", err)
			return nil
		}
		return db
	}
}

//105277355
func pessimisticLock(uid uint32) {
	startTime := time.Now()
	log.Printf("%d 开始抢购", uid)
	atomic.AddUint32(&total, 1)
	var db *gorm.DB
	dbi := mysqlPool.Get()
	if dbi == nil {
		log.Panicf("poll error")
		return
	}
	defer mysqlPool.Put(dbi)
	var (
		info SecKillModel
		err  error
	)
	db = dbi.(*gorm.DB)
	tx := db.Begin()
	query := tx.Table("sec_kill")
	err = query.Exec("select * from sec_kill where id=2 for update").Scan(&info).Error
	if err != nil {
		log.Printf("find error :%+v\n", err)
	}
	log.Printf("%d 查询库存：%d\n", uid, info.Amount)
	if info.Amount <= 0 {
		tx.Rollback()
		return
	}
	time.Sleep(time.Duration(rand.Float32()*100) * time.Millisecond)
	info.Amount--
	//准备更新
	query = tx.Table("sec_kill").
		Exec("update sec_kill set amount=? where id=?", info.Amount, info.Id)
	if query.RowsAffected != 0 {
		err = tx.Commit().Error
		if err != nil {
			log.Printf("commit error :%+v\n", err)
			tx.Rollback()
			return
		}
		atomic.AddUint32(&success, 1)
		log.Printf("%d 抢购成功！耗时：%f", uid, float64(time.Now().Sub(startTime))/float64(time.Second))
		return
	}
	if query.Error != nil {
		tx.Rollback()
		log.Printf("Save error :%+v\n", err)
		return
	}

}

//1450498491
func optimisticLock(uid uint32) {
	atomic.AddUint32(&total, 1)
	startTime := time.Now()
	log.Printf("%d 开始抢购", uid)
	var db *gorm.DB
	dbi := mysqlPool.Get()
	if dbi == nil {
		log.Panicf("poll error")
		return
	}
	defer mysqlPool.Put(dbi)
	var (
		info SecKillModel
		err  error
	)
	db = dbi.(*gorm.DB)
	query := db.Table("sec_kill")
	for {
		err = query.Where("id=?", 1).Find(&info).Error
		if err != nil {
			log.Printf("find error :%+v\n", err)
		}
		log.Printf("%d 查询库存：%d\n", uid, info.Amount)
		if info.Amount <= 0 {
			return
		}
		info.Version++
		info.Amount--
		time.Sleep(time.Duration(rand.Float32()*100) * time.Millisecond)
		//准备更新
		query := db.Table("sec_kill").
			Where("id=? and version<?", info.Id, info.Version).
			Update(updateFields{"amount": info.Amount, "version": info.Version})
		if query.RowsAffected != 0 {
			atomic.AddUint32(&success, 1)
			log.Printf("%d 抢购成功！耗时：%f", uid, float64(time.Now().Sub(startTime))/float64(time.Second))
			return
		}
		if query.Error != nil {
			log.Printf("Save error :%+v\n", err)
			return
		}
	}

}

func messageQueue() {

}
